package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"fmt"
	"math/rand"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/labstack/echo/v4"
)

type User struct {
	BaseModel
	Username string `json:"username" validate:"required" gorm:"uniqueIndex"`
	Password string `json:"password,omitempty" validate:"required,ascii"`
	Email    string `json:"email" validate:"required,email" gorm:"unique"`
	IsAdmin  bool   `json:"is_admin"`
	IsActive bool   `json:"is_active"`
	Key      []Key  `json:"-"`
}

type UserBrief struct {
	ID       uint   `json:"id"`
	Username string `json:"username" validate:"required" gorm:"uniqueIndex"`
	Email    string `json:"email" validate:"required,email" gorm:"unique"`
}

type Key struct {
	ID     uint      `json:"-" gorm:"primarykey"`
	Key    string    `gorm:"uniqueIndex" json:"key"`
	UserId uint      `json:"-"`
	Expire time.Time `json:"expire"`
}

func init() {
	rand.Seed(time.Now().UnixNano())
}

func AuthRegister(e *echo.Echo) {
	g := e.Group("/api/auth")
	g.POST("/login/", AuthLogin)
	g.GET("/profile/", AuthProfile, NeedAuth)
	g.POST("/newuser/", AuthNewUser)
}

func encodePassword(pw string) (string, error) {
	// result form '<algorithm>$<iterations>$<salt>$<hash>'
	alg := "pbkdf2_sha256"
	iters := 1000
	salt := make([]byte, 32)
	_, err := rand.Read(salt)
	if err != nil {
		return "", errors.New("生成密码盐值失败")
	}

	var hashed []byte
	h := sha256.New()
	h.Write([]byte(pw))
	hashed = h.Sum(nil)
	for i := 1; i < iters; i++ {
		h.Write(hashed)
		hashed = h.Sum(nil)
	}
	h.Write(salt)
	hashed = h.Sum(nil)

	return fmt.Sprint(alg, "$", iters, "$", base64.StdEncoding.EncodeToString(salt), "$", base64.StdEncoding.EncodeToString(hashed)), nil
}

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")

// 随机生成 key
func randString(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}

func comparePassword(input, encoded string) (bool, error) {
	decoded := strings.Split(encoded, "$")
	if len(decoded) != 4 {
		return false, errors.New("密码格式错误——应该包含 4 个 `$` 分隔的字段")
	}
	iters, err := strconv.Atoi(decoded[1])
	if err != nil {
		return false, errors.New("密码格式错误——迭代次数非数字")
	}
	salt, err := base64.StdEncoding.DecodeString(decoded[2])
	if err != nil {
		return false, errors.New("密码格式错误——哈希值包含非字符")
	}
	hash, err := base64.StdEncoding.DecodeString(decoded[3])
	if err != nil {
		return false, errors.New("密码格式错误——哈希值包含非字符")
	}

	hashedInput := make([]byte, 0, 100)
	h := sha256.New()
	h.Write([]byte(input))
	hashedInput = h.Sum(nil)
	for i := 1; i < iters; i++ {
		h.Write(hashedInput)
		hashedInput = h.Sum(nil)
	}
	h.Write(salt)
	hashedInput = h.Sum(nil)

	return bytes.Equal(hashedInput, hash), nil
}

func AuthLogin(c echo.Context) error {
	db := newDBSession(c)
	var user User
	if err := c.Bind(&user); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, err.Error())
	}
	input_password := user.Password
	if result := db.Where(&User{Username: user.Username, IsActive: true}).First(&user); result.Error != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "用户名不存在或账户被禁用").SetInternal(result.Error)
	}

	if valid, err := comparePassword(input_password, user.Password); err == nil {
		if valid {
			key := Key{Key: randString(32), Expire: time.Now().Local().Add(time.Second * 60 * 60 * 24 * 7)}
			if err := db.Model(&user).Association("Key").Append(&key); err != nil {
				return errors.New("生成 Key 失败，数据库操作错误")
			}
			return c.JSON(http.StatusOK, &Key{Key: key.Key, Expire: key.Expire})
		} else {
			return echo.NewHTTPError(http.StatusBadRequest, "用户名或密码错误")
		}
	} else {
		return err
	}
}

func getUser(c echo.Context) (User, error) {
	// 获取用户信息，直接从 Context 获取
	var user *User
	user, ok := c.Request().Context().Value("user").(*User)
	if !ok {
		return *user, errors.New("获取用户失败，调用这个函数需要通过 Key 认证才行")
	}
	return *user, nil
}

func AuthProfile(c echo.Context) error {
	if user, err := getUser(c); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest).SetInternal(err)
	} else {
		return c.JSON(http.StatusOK, user)
	}
}

func AuthNewUser(c echo.Context) error {
	db := newDBSession(c)
	var user User
	if err := c.Bind(&user); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "请检查请求体是否正确")
	}
	var newuser User
	newuser.Username = user.Username
	newuser.Password = user.Password
	newuser.Email = user.Email
	newuser.IsActive = true
	if err := c.Validate(newuser); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "格式不对")
	}
	if pwd, err := encodePassword(user.Password); err != nil {
		return err
	} else {
		newuser.Password = pwd
		if result := db.Create(&newuser); result.Error != nil {
			return echo.NewHTTPError(http.StatusBadRequest, "用户名已存在？")
		}
	}
	return c.NoContent(http.StatusOK)
}
